#include "tbcore/base/at_exit_manager.hpp"

#include <stack>

#include "tbcore/base/basic_types.hpp"
#include "tbcore/base/logging.hpp"
#include "tbcore/base/mutex.hpp"
#include "tbcore/base/assert.hpp"

TB_NAMESPACE_BEGIN

static AtExitManager* gExitManager = nullptr;

struct AtExitManager::AtExitManagerImpl {
  AtExitManagerImpl(AtExitManager* nextExitManager) 
    : nextExitManager_(nextExitManager) {}

  AtExitManager* nextExitManager_;
  std::stack<ExitFunctorType> stack_;
  SpinMutex stackMutex_;
};

AtExitManager::AtExitManager()
  :impl_(new AtExitManagerImpl(gExitManager)) {
  gExitManager = this;
}

AtExitManager::~AtExitManager() {
  if (!gExitManager) {
    NOTREACHED() << "failed to destruct AtExitManager without a instance";
    return;
  }

  TB_ASSERT(gExitManager == this);

  ProcessExitNow();

  gExitManager = impl_->nextExitManager_;
  delete impl_;
}

void AtExitManager::RegisterAtExit( const ExitFunctorType& func ) {
  if (!gExitManager) {
    NOTREACHED() << "failed to RegisterAtExit without a AtExitManager";
    return;
  }

  SpinMutex::ScopedLock lock(gExitManager->impl_->stackMutex_);
  gExitManager->impl_->stack_.push(func);
}

void AtExitManager::ProcessExitNow() {
  if (!gExitManager) {
    NOTREACHED() << "failed to RegisterAtExit without a AtExitManager";
    return;
  }

  SpinMutex::ScopedLock lock(gExitManager->impl_->stackMutex_);
  while (!gExitManager->impl_->stack_.empty()) {
    TB_IGNORE_EXCEPTION_TRY(gExitManager->impl_->stack_.top()());
    gExitManager->impl_->stack_.pop();
  }
}

TB_NAMESPACE_END